﻿using System;
using System.Collections.Generic;
using AlgorytmyGenetyczne.Wyjatki;

namespace AlgorytmyGenetyczne.Operatory
{
    public class Krzyzowanie : IOperator
    {
        public double Prawdopodobienstwo { get; set; }
        public int IloscPunktowKrzyzowania { get; set; }
        private readonly Random losowanie;

        public Krzyzowanie(double prawdopodobienstwo, int iloscPunktowKrzyzowania)
        {
            Prawdopodobienstwo = prawdopodobienstwo;
            IloscPunktowKrzyzowania = iloscPunktowKrzyzowania;
            losowanie = new Random();
        }

        public Krzyzowanie(double prawdopodobienstwo)
        {
            Prawdopodobienstwo = prawdopodobienstwo;
            IloscPunktowKrzyzowania = 1;
            losowanie = new Random();
        }

        public Krzyzowanie()
        {
            Prawdopodobienstwo = 0.25;
            IloscPunktowKrzyzowania = 1;
            losowanie = new Random();
        }

        public Populacja wykonaj(Populacja populacja)
        {
            if(IloscPunktowKrzyzowania-1 > populacja.Osobnicy[0].Genotyp.Geny.Length)
            {
                throw new KrzyzowanieException("Ilosc punktow krzyżowania jest większa niż ilość genów w genotypie");
            }
            List<Osobnik> listaOsobnikow = populacja.Osobnicy;
            List<Osobnik> listaKandydatow = new List<Osobnik>();
            
            //wybieramy losowo na podstawie prawdopodobienstwa, ktore osobniki beda sie krzyzowaly
            //jesli wartosc losowa bedzie mniejsza od prawdopodobienstwa, to osobnik staje sie kandydatem
            //zostaje usuniety z listy populacji, bo trafia tam jego potomkowie.
            List<Osobnik> listaOsobnikoWDoUsunieciaZPopulacji = new List<Osobnik>();
            foreach (Osobnik osobnik in listaOsobnikow)
            {
                if (losowanie.NextDouble() < Prawdopodobienstwo)
                {
                    listaKandydatow.Add(osobnik);
                    listaOsobnikoWDoUsunieciaZPopulacji.Add(osobnik);
                }
            }
            foreach (Osobnik osobnik in listaOsobnikoWDoUsunieciaZPopulacji)
            {
                listaOsobnikow.Remove(osobnik);
            }
            listaOsobnikoWDoUsunieciaZPopulacji.Clear();

            //jesli liczba kandydatow jest nieprzysta, to albo dodajemy albo odejmujemy jednego osobnika
            if(listaKandydatow.Count % 2 != 0)
            {
                if(losowanie.NextDouble() > 0.5)
                {
                    dodajLosowegoOsobnikaDoListyKandydatow(listaKandydatow, listaOsobnikow);
                }else
                {
                    usunLosowegoOsobnikaZListyKandydatow(listaKandydatow, listaOsobnikow);
                }
            }

            //Dobranie chromosomow w pary i wymiana genami. Chromosomy ktore wziely udzial w krzyzowaniu
            //sa usuwane z listy kandydatow
            Osobnik rodzic1;
            Osobnik rodzic2;
            Osobnik[] dzieci = new Osobnik[2];
            int licznik = 0;
            while(listaKandydatow.Count > 0)
            {
                //wybieramy pierwszego kandydata
                int los = losowanie.Next(listaKandydatow.Count - 1);
                rodzic1 = listaKandydatow[los];
                listaKandydatow.Remove(rodzic1);
                los = losowanie.Next(listaKandydatow.Count - 1);
                rodzic2 = listaKandydatow[los];
                listaKandydatow.Remove(rodzic2);
                dzieci = krzyzujOsobnikow(rodzic1, rodzic2);
                
                //System.Console.WriteLine(++licznik);
                listaOsobnikow.AddRange(dzieci);
            }
            return populacja;
        }

        public Osobnik[] krzyzujOsobnikow(Osobnik rodzic1, Osobnik rodzic2)
        {
            int resztaDzielenia = 0;
            if(losowanie.NextDouble() < 0.5)
            {
                resztaDzielenia = 1;
            }
            Osobnik dziecko1 = new Osobnik();
            dziecko1.Genotyp = rodzic1.Genotyp;
            Osobnik dziecko2 = new Osobnik();
            dziecko2.Genotyp = rodzic2.Genotyp;
            Osobnik[] dzieci = {dziecko1, dziecko2};
            List<int> listaPunktowKrzyzowania = wylosujPunktyKrzyżowania(dziecko1.Genotyp.Geny.Length);

            for (int numerPunktu = 0; numerPunktu < listaPunktowKrzyzowania.Count; numerPunktu++)
            {
                if (numerPunktu % 2 != 0)
                {
                    int indeksPoczatkuZamiany = listaPunktowKrzyzowania[numerPunktu];
                    int indeksKoncaZamiany;
                    //jesli rozpatrywany punkt krzyzowania jest ostatnim, to trzeba zamieniac geny
                    //do konca genotypu
                    if(numerPunktu == listaPunktowKrzyzowania.Count-1)
                    {
                        indeksKoncaZamiany = dziecko1.Genotyp.Geny.Length;
                    }else
                    {
                        indeksKoncaZamiany = listaPunktowKrzyzowania[numerPunktu + 1];
                    }
                    for (int i = indeksPoczatkuZamiany; i < indeksKoncaZamiany; i++)
                    {
                        bool bufor = dziecko1.Genotyp.Geny[i];
                        dziecko1.Genotyp.Geny[i] = dziecko2.Genotyp.Geny[i];
                        dziecko2.Genotyp.Geny[i] = bufor;
                    }
                }

            }
            
            return dzieci;
        }


        private void dodajLosowegoOsobnikaDoListyKandydatow(List<Osobnik> listaKandydatow, List<Osobnik> listaOsobnikow)
        {
            int numerOsobnika = losowanie.Next(listaOsobnikow.Count - 1);
            Osobnik wylosowanyOsobnik = listaOsobnikow[numerOsobnika];
            listaKandydatow.Add(wylosowanyOsobnik);
            listaOsobnikow.Remove(wylosowanyOsobnik);
        }

        private void usunLosowegoOsobnikaZListyKandydatow(List<Osobnik> listaKandydatow, List<Osobnik> listaOsobnikow)
        {
            int numerOsobnika = losowanie.Next(listaKandydatow.Count - 1);
            Osobnik wylosowanyOsobnik = listaKandydatow[numerOsobnika];
            listaKandydatow.Remove(wylosowanyOsobnik);
            listaOsobnikow.Add(wylosowanyOsobnik);   
        }

        private List<int> wylosujPunktyKrzyżowania(int dlugoscGenotypu)
        {
            List<int> listaPunktowKrzyzowania = new List<int>();

            //dodajemy pierwszy indeks genotypu jako punkt poczatkowy, bo inaczej zacznie zle
            //zamiane czesci genotypu
            listaPunktowKrzyzowania.Add(0);
            int doWylosowania = IloscPunktowKrzyzowania;
            while (doWylosowania > 0)
            {
                int punkt = losowanie.Next(dlugoscGenotypu - 1);
                if (!listaPunktowKrzyzowania.Contains(punkt))
                {
                    listaPunktowKrzyzowania.Add(punkt);
                    doWylosowania--;
                }  
            }
            listaPunktowKrzyzowania.Sort((punkt1, punkt2) => punkt1.CompareTo(punkt2));
            return listaPunktowKrzyzowania;
        }
    }
}
